home *** CD-ROM | disk | FTP | other *** search
/ Practical Internet Web Designer 86 / PIWD86.iso / pc / contents / dreamweaver / software / dwmx2004.exe / Disk1 / data1.cab / Configuration_En / Commands / Clean Up HTML.js < prev    next >
Encoding:
JavaScript  |  2003-09-05  |  40.5 KB  |  1,221 lines

  1. //
  2. // Copyright 2000, 2001, 2002, 2003 Macromedia, Inc. All rights reserved.
  3. // ----------------------------------------------------
  4. //
  5. // Clean Up HTML.js
  6. //
  7. // This command cleans up certain categories of superfluous
  8. // HTML within the user document without effecting document
  9. // layout.  This command makes two passes over the users
  10. // document depending on the options selected; see
  11. // cleanUpDocument() for more details.
  12. //
  13. // Version 3.0
  14. // Added new array class for cleanup HTML contains function.
  15. // ----------------------------------------------------
  16.  
  17. //
  18. // Global variables -- see initialize() for more comments.
  19. // Dreamweaver doesn't currently initialize any globals
  20. // loaded in auxilliary scripts through <SCRIPT SRC=..>,
  21. // so we explicitly initialize these initialize().
  22. //
  23. var helpDoc = MM.HELP_cmdCleanUpHTML;
  24.  
  25. var cbRemoveEmptyTags;
  26. var cbRemoveRedundant;
  27. var cbCombineFonts;
  28. var cbRemoveTags;
  29. var cbRemoveComments;
  30. var cbRemoveDWComments;
  31. var cbShowLog;
  32. var tbTagsToRemove;
  33. var numEmptyRemoved;
  34. var numRedundantRemoved;
  35. var numTagsRemoved;
  36. var numCommentsRemoved;
  37. var numFontsCombined;
  38. var arrTagsToRemove = new Array(); // Array
  39. var strClassAttrib;
  40. var strStyleAttrib;
  41. var arrDWCommentTags = new Array();
  42. var arrDWCommentNonTags = new Array();
  43. var arrDWTagPrefixes = new Array();
  44. var emptyRemovalCandidates = new Array();
  45. var redundantTagCandidates = new Array();
  46. var combinableTagCandidates = new Array();
  47. var bPreserveEmptyHeader;
  48. var bRemovedTracing;
  49. var arrXHTMLresults;
  50.  
  51.  
  52. //------------------ Commands API --------------------
  53.  
  54. function commandButtons()
  55. {
  56.    return new Array( MM.BTN_OK,     "cleanUpDocument()"  // main entry point
  57.                    , MM.BTN_Cancel, "window.close()"
  58.                    , MM.BTN_Help,   "displayHelp()");
  59. }
  60.  
  61. function canAcceptCommand()
  62. {
  63.   var retVal = false;
  64.   if (dw.getDocumentDOM() && dw.getDocumentDOM().getParseMode() == 'html' && (dw.getFocus() == 'document' || dw.getFocus(true) == 'html' || dw.getFocus() == 'textView')){
  65.     retVal = true;
  66.   }
  67.   return retVal;
  68. }
  69.  
  70. //
  71. // ------- Local Clean Up command functions ----------
  72. //
  73.  
  74. // Return true if curArr contains an entry equal to item.
  75. function arrayContains( curArr, item )
  76. {
  77.    var nElements = curArr.length;
  78.    for( var i = 0; i < nElements; i++ )
  79.       if ( curArr[i] == item )
  80.          return true;
  81.  
  82.    return false;
  83. }
  84.  
  85. // Return true if curArr contains an entry that is a string prefix of item.
  86. function arrayContainsPrefix( curArr, item )
  87. {
  88.    var itemLength = item.length;
  89.    var nElements = curArr.length;
  90.    for( var i = 0; i < nElements; i++ )
  91.    {
  92.       var curItem = curArr[i];
  93.       if ( curItem.length <= itemLength &&
  94.          curItem == item.substring(0, curItem.length) )
  95.          return true;
  96.    }
  97.  
  98.    return false;
  99. }
  100.  
  101. function isQuote( c )
  102. {
  103.    return( c == '\"' || c == '\'' );
  104. }
  105.  
  106. function isAlpha( c )
  107. {
  108.    var isoval = c.charCodeAt(0);
  109.    return( (isoval >= "A".charCodeAt(0) && isoval <= "Z".charCodeAt(0)) ||
  110.            (isoval >= "a".charCodeAt(0) && isoval <= "z".charCodeAt(0)));
  111. }
  112.  
  113. // Match a <xxx or </xxx tag; note that xxx must be alphabetical
  114. function isTagBegin( currentchar, nextchar )
  115. {
  116.    return( currentchar == '<' && (isAlpha( nextchar ) || nextchar == '/') );
  117. }
  118.  
  119. // Note that '>' should be ignored within quotes inside tag brackets
  120. function isTagEnd( c )
  121. {
  122.    return( c == '>' );
  123. }
  124.  
  125. function isWhite( c )
  126. {
  127.    return( c == ' ' || c == '\t' || c == '\n' || c == '\r' );
  128. }
  129.  
  130. function isAllWhite( str )
  131. {
  132.    for( var i = 0; i < str.length; i++ )
  133.    {
  134.       if ( !isWhite( str.charAt( i ) ) )
  135.          return( false );
  136.    }
  137.  
  138.    return( true );
  139. }
  140.  
  141. // parseAttributes()
  142. //
  143. // Parse the attributes from within the start tag of a given
  144. // node per the rules found here: http://www.w3.org/TR/WD-html-lex/
  145. //
  146. // Return an array of arrays (unfortunately; the associative
  147. // aspect of arrays is overloaded with "instance properties",
  148. // so arrays already contain prototype methods/properties
  149. // value pairs):
  150. //
  151. //          arr[0] --> attributes array
  152. //          arr[1] --> values array
  153. //
  154. // The value for a given attribute are in the same position
  155. // at the attribute within the values array.  Singleton name
  156. // tokens have an empty ("") or undefined value.
  157. //
  158. // If bStripQuotes is true, then any "outer" quotes around an
  159. // attribute value are stripped, e.g., the value in
  160. //
  161. //    NAME="bob's name"
  162. //
  163. // is returned as: bob's name.  If bStripQuotes is false, that
  164. // value is returned as "bob's name"
  165. //
  166. // If bMakeUpper is true, all attribute/value strings are normalized
  167. // to upper case
  168. //
  169. function parseAttributes( node, bStripQuotes, bMakeUpper )
  170. {
  171.    var   tagstr         = node.outerHTML;
  172.    var   pos            = 0;
  173.    var   prevChar       = null;
  174.    var   currentChar    = null;
  175.    var   currentQuote   = null;
  176.    var   arrAttribs     = new Array();
  177.    var   arrValues      = new Array();
  178.    var   arrIdx         = 0;
  179.    var   attrib         = "";
  180.    var   value          = "";
  181.    var   bValueIsEmpty  = false;
  182.    var   bInsideQuote   = false;
  183.    var   bAccumValue    = false;
  184.    var   bAttribReady   = false;
  185.    var   bSkipToWhite   = true;  // initially true to skip "<tag "
  186.  
  187.    while( pos < node.outerHTML.length )
  188.    {
  189.       prevChar     = currentChar;
  190.       currentChar  = tagstr.charAt( pos++ );
  191.  
  192.       // Handle quote state; remember actual quote that
  193.       // flipped the state so we match ' and " right
  194.       //
  195.       if ( isQuote( currentChar ) )
  196.       {
  197.          if ( bInsideQuote )
  198.          {
  199.             if ( currentChar == currentQuote )
  200.             {
  201.                // Coming out of quoted region; turn quotes off
  202.                bInsideQuote = false;
  203.                currentQuote = null;
  204.                if ( bStripQuotes )
  205.                {
  206.                   // Careful; make sure ATTR="" works even when we're
  207.                   // stripping quotes off values
  208.                   MM_assert( bAccumValue, MSG_ParseErrEndQuote );
  209.                   bValueIsEmpty = true;
  210.                   continue;
  211.                }
  212.             }
  213.          }
  214.          else
  215.          if ( bAccumValue && value == "" ) // only turn quotes on after '=' and
  216.          {                                 // before accumulating anything; e.g.,
  217.             // Turn quotes on              // ignore the quote in ATTR=xxx"xxx
  218.             bInsideQuote = true;
  219.             currentQuote = currentChar;
  220.             if ( bStripQuotes )
  221.                continue;
  222.          }
  223.       }
  224.  
  225.       // Handle the terminating character; write any attribute/value
  226.       // we may have been accumulating and we're done.
  227.       //
  228.       if ( !bInsideQuote && isTagEnd( currentChar ) )
  229.       {
  230.          if ( attrib != "" )
  231.          {
  232.             arrAttribs[ arrIdx ]  = bMakeUpper ? attrib.toUpperCase() : attrib;
  233.             arrValues[ arrIdx++ ] = bMakeUpper ? value.toUpperCase() : value;
  234.             attrib = "";
  235.             value  = "";
  236.             bAttribReady = false;
  237.             bAccumValue  = false;
  238.          }
  239.          break;
  240.       }
  241.  
  242.       // Accumulate characters; if bAccumValue is true, we're on the
  243.       // right side of an "=", otherwise we're on the left side or accumulating
  244.       // a singleton name token.  I don't think quoted regions make sense
  245.       // on the left side either.
  246.       //
  247.       if ( !bInsideQuote && !bAccumValue )
  248.       {
  249.          // first skip to white after tag name <xxxx
  250.          if ( bSkipToWhite && !isWhite( currentChar ) )
  251.             continue;
  252.  
  253.          bSkipToWhite = false;
  254.  
  255.          // Whitespace not inside quotes; if we're accumulating
  256.          // an attribute, it's ready (the whitespace terminates it);
  257.          if ( isWhite( currentChar ) )
  258.          {
  259.             bAttribReady = attrib != "";
  260.          }
  261.          else
  262.          {
  263.             // Non-white space; if we have an equals sign, switch
  264.             // over to accumulate the value
  265.             if ( currentChar == '=' )
  266.             {
  267.                bAttribReady = attrib != "";
  268.                bAccumValue  = true;
  269.                MM_assert( bAttribReady, MSG_ParseErrUnexpectedEQU );
  270.             }
  271.             else
  272.             {
  273.                // Unquoted non-white non-value -- accumulate
  274.                // as name token.  If there's a name token ready,
  275.                // save it as a singleton first.
  276.                //
  277.                if ( bAttribReady )
  278.                {
  279.                   arrAttribs[ arrIdx++ ] = bMakeUpper ? attrib.toUpperCase() : attrib;
  280.                   attrib = "";
  281.                   bAttribReady = false;
  282.                }
  283.  
  284.                attrib += currentChar;
  285.             }
  286.          }
  287.       }
  288.       else
  289.       {
  290.          // We're accumulating a value
  291.          //
  292.          MM_assert( bAttribReady, MSG_ParseErrUnexpectedEQU );
  293.  
  294.          if ( !bInsideQuote && isWhite( currentChar ) )
  295.          {
  296.             // Swallow whitespace until we either get a value
  297.             // or we terminate
  298.  
  299.             if ( value != "" || bValueIsEmpty )
  300.             {
  301.                arrAttribs[ arrIdx ]  = bMakeUpper ? attrib.toUpperCase() : attrib;
  302.                arrValues[ arrIdx++ ] = bMakeUpper ? value.toUpperCase() : value;
  303.                attrib = "";
  304.                value  = "";
  305.                bAttribReady  = false;
  306.                bAccumValue   = false;
  307.                bValueIsEmpty = false;
  308.             }
  309.          }
  310.          else
  311.          {
  312.             // We're inside a quote, or we're not terminated -- keep
  313.             // accumulating
  314.             //
  315.             value += currentChar;
  316.          }
  317.       }
  318.    }
  319.  
  320.    // We're done; package up our arrays and return them
  321.    //
  322.    MM_assert( !bAccumValue, MSG_ParseErrValue );
  323.    return new Array( arrAttribs, arrValues );
  324. }
  325.  
  326. // findCombinableParent()
  327. //
  328. // Return a parent node with which the given node may have
  329. // its attributes combined with.  This routine trusts that
  330. // caller has verified that the combineTagName is a member
  331. // of combinableTagCandidates!  A combinable parent is
  332. // a direct parent up the tree who is the parent of no
  333. // other children (which would not want to inherit the
  334. // characteristics of the given child whose attributes
  335. // will migrate up), e.g.:
  336. //
  337. // <FONT face="arial"><FONT color="blue">text</FONT></FONT>
  338. //
  339. // and
  340. //
  341. // <FONT face="arial"><B><FONT color="blue">text</FONT></B></FONT>
  342. //
  343. // are combinable, but
  344. //
  345. // <FONT face="arial"><B>x<FONT color"blue">text</FONT></B></FONT>
  346. //
  347. // is not as the 'x' textual child should not inherit the
  348. // blue characteristic.  This routine walks the "direct"
  349. // (childNodes.length == 1) parent chain.
  350. //
  351. function findCombinableParent( node, combineTagName )
  352. {
  353.    MM_assert( arrayContains(combinableTagCandidates, combineTagName ) );
  354.  
  355.    var rtnNode = null;
  356.  
  357.    while ( (node.parentNode != null)    &&
  358.         (node.parentNode.childNodes.length == 1) )
  359.    {
  360.       if ( combineTagName == node.parentNode.tagName ) {
  361.          rtnNode = node.parentNode;
  362.          break;
  363.       }
  364.       if ( node.parentNode.innerHTML == node.outerHTML ) {// parent contains only this child tree
  365.          node = node.parentNode;
  366.       } else {
  367.          break;
  368.       }
  369.    }
  370.  
  371.    return rtnNode;
  372. }
  373.  
  374. // hasRedundantParent()
  375. //
  376. // Return true if the given node is redundant with a
  377. // controlling parent.  Redundant parent/children must
  378. // have identical attribute/value sets.
  379. //
  380. function hasRedundantParent( node )
  381. {
  382.    var rc = false;
  383.  
  384.    if ( arrayContains(redundantTagCandidates,  node.tagName ) )
  385.    {
  386.       var parent  = node.parentNode;
  387.  
  388.       // Find controlling parent
  389.       while( parent != null )
  390.       {
  391.          if ( node.tagName == parent.tagName )
  392.          {
  393.             // Compare parent and child attribute name/value pairs
  394.             var cArrs   = parseAttributes( node, true, true );
  395.             var pArrs   = parseAttributes( parent, true, true );
  396.             var cNames  = cArrs[0];
  397.             var cValues = cArrs[1];
  398.             var pNames  = pArrs[0];
  399.             var pValues = pArrs[1];
  400.  
  401.             if ( cNames.length == pNames.length && cValues.length == pValues.length )
  402.             {
  403.                cNames.sort();
  404.                pNames.sort();
  405.                cValues.sort();
  406.                pValues.sort();
  407.  
  408.                var len = cNames.length;
  409.                for( var i = 0; i < len; i++ )
  410.                {
  411.                   // note in js: undefined == undefined is true
  412.                   if ( pNames[i]  != cNames[i] || cValues[i] != pValues[i] )
  413.                      break;
  414.                }
  415.  
  416.                rc = (i == len); // if we got through everything they're the same
  417.             }
  418.  
  419.             if ( rc )  // if we're redundant, we're done
  420.                break;
  421.  
  422.             // Otherwise, if we're not actually overriding anything on this
  423.             // parent, we may still be redundant with an uber parent.  Cycle through
  424.             // the child's attributes and if none are present on parent keep going
  425.             //
  426.             var bKeepGoing = true;
  427.             for( var i = 0; i < cNames.length; i++ )
  428.             {
  429.                if ( arrayContains(pNames,  cNames[i] ) )
  430.                {
  431.                   bKeepGoing = false;
  432.                   break;
  433.                }
  434.             }
  435.  
  436.             if ( !bKeepGoing )
  437.                break;
  438.          } else if ( node.tagName == 'TABLE' || parent.tagName == 'TABLE') {
  439.             break;
  440.          }
  441.  
  442.          parent = parent.parentNode;
  443.       }
  444.    }
  445.  
  446.    return rc;
  447. }
  448.  
  449. // isAllWhiteNodeSignificant()
  450. //
  451. // Given a node whose inner html is all white, this
  452. // routine examines the node's siblings and returns
  453. // true if the whitespace is significant and false
  454. // otherwise.
  455. //
  456. function isAllWhiteNodeSignificant( node )
  457. {
  458.  
  459.    if (!node.parentNode) return false;
  460.  
  461.    var siblings   = node.parentNode.childNodes;
  462.    var nSiblings  = siblings.length;
  463.    var siblingIdx = 0;
  464.  
  465.    // If we're an only child, then we really need
  466.    // to look at uncles and aunts.
  467.    if ( (nSiblings == 1) && (node.parentNode != null) && (node.parentNode.nodeType != Node.DOCUMENT_NODE) )
  468.       return( isAllWhiteNodeSignificant( node.parentNode ) );
  469.  
  470.    // Find self as parent's child first
  471.    for( ; siblingIdx < nSiblings; siblingIdx++ )
  472.       if ( siblings.item( siblingIdx ) == node )
  473.          break;
  474.  
  475.    MM_assert( siblingIdx < nSiblings, MSG_ErrParentChild );
  476.  
  477.    // If sibling to the left has trailing whitespace,
  478.    // our current all white node isn't significant.  Note
  479.    // we can just look to our immediate left rather than go
  480.    // to zero because any empty siblings to the left will
  481.    // have already been gobbled.
  482.    //
  483.    var lSibling = siblingIdx > 0 ? siblings.item( siblingIdx - 1) : null;
  484.    if ( lSibling != null )
  485.    {
  486.       if ( lSibling.nodeType == Node.TEXT_NODE )
  487.       {
  488.          if ( (lSibling.data.length > 0) &&
  489.               isWhite( lSibling.data[ lSibling.data.length - 1 ] ) )
  490.             return false;
  491.       }
  492.       else
  493.       if ( lSibling.nodeType == Node.ELEMENT_NODE )
  494.       {
  495.          // non text left sibling
  496.          if ( (lSibling.innerHTML.length > 0) &&
  497.               isWhite( lSibling.innerHTML[ lSibling.innerHTML.length - 1 ] ) )
  498.             return false;
  499.       }
  500.       // else go on to our right to determine our significance
  501.    }
  502.  
  503.    // Now see if there's significant leading whitespace to
  504.    // the immediate right that might render our all white
  505.    // current node insignificant
  506.    //
  507.    var rSibling = null;
  508.    siblingIdx++;
  509.    while( siblingIdx < nSiblings )
  510.    {
  511.       rSibling = siblings.item( siblingIdx );
  512.  
  513.       if ( rSibling.nodeType == Node.TEXT_NODE )
  514.       {
  515.          // We have a textual sibling to the right; if
  516.          // this guy doesn't have leading whitespace,
  517.          // we're significant, otherwise we're not.
  518.          if ( rSibling.data.length > 0 )
  519.             return( !isWhite( rSibling.data[0] ) );
  520.  
  521.          // else empty text node
  522.       }
  523.       else
  524.       if ( rSibling.nodeType == Node.ELEMENT_NODE )
  525.       {
  526.          // We have a non-empty non-text node to the
  527.          // right; if this guy doesn't have leading
  528.          // whitespace we're significant, otherwise not
  529.          if ( rSibling.innerHTML.length > 0 )
  530.             return( !isWhite( rSibling.innerHTML[0] ) );
  531.  
  532.          // else empty non-text node...
  533.       }
  534.  
  535.       siblingIdx++;
  536.    }
  537.  
  538.    // If we got here there's nothing interesting to the
  539.    // right of this all white node, so it's as if we're
  540.    // an only child.  The DOCUMENT_NODE check is just for
  541.    // safety; there shouldn't be a way to get that high on
  542.    // empty markup node removal...
  543.  
  544.    if ( node.parentNode != null && node.nodeType != Node.DOCUMENT_NODE )
  545.       return( isAllWhiteNodeSignificant( node.parentNode ) );
  546.  
  547.    // otherwise nothing left -- we really are insignificant...
  548.    return false;
  549. }
  550.  
  551. // isRemovableEmptyTag()
  552. //
  553. // Return true if this tag can be safely removed from the
  554. // document, false otherwise.
  555. //
  556. function isRemovableEmptyTag( tagNode )
  557. {
  558.    // First this tag must be an empty removal candidate with no class info
  559.    //
  560.    if ( arrayContains(emptyRemovalCandidates,  tagNode.tagName ) && !hasClassAttribute( tagNode ) )
  561.    {
  562.       // Short-circuit for named anchor tags; empty named anchors
  563.       // should be left alone
  564.       if ( "A" == tagNode.tagName && (null != tagNode.getAttribute( "NAME" )) )
  565.          return false;
  566.  
  567.       // If the innerHTML length is zero, it's empty and
  568.       // can be safely removed *unless* it's a heading
  569.       // tag -- the first empty heading tag after text
  570.       // forces a carriage return.
  571.       //
  572.       if ( tagNode.innerHTML.length == 0 )
  573.       {
  574.          return true;
  575.       }
  576.       else
  577.       if ( isAllWhite( tagNode.innerHTML ) && !isAllWhiteNodeSignificant( tagNode ) )
  578.       {
  579.          // All empty tag candidates (generally character markup)
  580.          // spanning only whitespace can also be removed if the
  581.          // tag is not within text, or if the tag to the right of
  582.          // text that ends in whitespace or to the left of text
  583.          // that begins with whitespace....
  584.          return true;
  585.       }
  586.    }
  587.  
  588.    return false;
  589. }
  590.  
  591. // Using a tracing image in Dreamweaver attaches up to four proprietary
  592. // attributes to the body tag. We want to remove these attributes if Remove
  593. // Dreamweaver Comments is checked.
  594. //
  595. function removeTracingAttrs()
  596. {
  597.   var bodyNode = dreamweaver.getDocumentDOM('document').body;
  598.  
  599.   //look for tracing attributes - if any are found, toggle
  600.   //the global boolean to true and remove all attributes
  601.   if (cbRemoveDWComments.checked){
  602.     if (bodyNode.getAttribute("tracingsrc") ||
  603.         bodyNode.getAttribute("tracingopacity") ||
  604.         bodyNode.getAttribute("tracingx") ||
  605.         bodyNode.getAttribute("tracingy"))
  606.    {
  607.      //remove all tracing image attributes
  608.      bodyNode.removeAttribute("tracingsrc");
  609.      bodyNode.removeAttribute("tracingopacity");
  610.      bodyNode.removeAttribute("tracingx");
  611.      bodyNode.removeAttribute("tracingy");
  612.      bRemovedTracing=true;
  613.    }
  614.   }
  615. }
  616.  
  617. // hasStyleAttribute()
  618. //
  619. // Return true if the given ELEMENT tag has a STYLE set
  620. //
  621. function hasStyleAttribute( tagNode )
  622. {
  623.    return( tagNode.getAttribute( strStyleAttrib ) != null );
  624. }
  625.  
  626. // hasClassAttribute()
  627. //
  628. // Return true if the given ELEMENT tag has a CLASSID set
  629. //
  630. function hasClassAttribute( tagNode )
  631. {
  632.    return( tagNode.getAttribute( strClassAttrib ) != null );
  633. }
  634.  
  635. // loadCommentOffsets()
  636. //
  637. // This callback is used by the comment removal traversal
  638. // to push offsets of comment nodes into the userData variable
  639. // passed by the comment removal pass.  Depending on the
  640. // user's options, we might be deleting non-Dreamweaver comments,
  641. // or DW comments that are actually represented as comments.
  642. //
  643. function loadCommentOffsets( commentNode, userData )
  644. {
  645.    // MM_note( "Processing NDW comment:" + commentNode.data );
  646.  
  647.    // Server-side include comments of the form "<!-- #include... -->"
  648.    // should always be left alone!
  649.  
  650.    // eat up any leading white in comment data
  651.    var i;
  652.    for( i = 0; i < commentNode.data.length; i++ )
  653.       if ( !isWhite( commentNode.data.charAt( i ) ) )
  654.          break;
  655.  
  656.    var bIsDWComment = arrayContains(arrDWCommentNonTags, commentNode.data);
  657.  
  658.    if (cbRemoveComments.checked)
  659.    {
  660.       // if we have a #include skip it, otherwise push offsets for
  661.       // removal
  662.       //
  663.       var bSkipSSIinclude = (commentNode.data.substr( i, 8 ).toLowerCase() == "#include");
  664.       var bSkipSSIecho = commentNode.data.substr( i, 5 ).toLowerCase() == "#echo";
  665.       var bSkipFWtable = commentNode.data.substr( i, 7 ).toLowerCase() == "fwtable";
  666.       var bSkipXML = commentNode.data.substr( i, 3 ).toLowerCase() == "xml";
  667.       var bSkipDoctype = commentNode.data.substr( i, 7 ).toLowerCase() == "doctype";
  668.       var bSkipFWBeginCopy = (commentNode.data.indexOf("BEGIN COPYING THE HTML") != -1);
  669.       var bSkipFWEndCopy = (commentNode.data.indexOf("STOP COPYING THE HTML HERE") != -1);
  670.       var isComment = commentNode.data.charAt(0) != "<";
  671.       
  672.       if ( !bSkipSSIinclude && !bSkipSSIecho && !bSkipDoctype && !bSkipXML && isComment && !bSkipFWtable && !bSkipFWBeginCopy && !bSkipFWEndCopy && !bIsDWComment){
  673.         // This is a not-very-elegant way of checking whether we've found a comment in
  674.         // the current document, or one inside an included file. It won't work if the
  675.         // comment in the included file happens to be the exact same length as the
  676.         // include statement, but it's a start.
  677.         var inCurrentDoc = true;
  678.         var nodeOffsets = dw.getDocumentDOM().nodeToOffsets(commentNode);
  679.         // +7 is the "<!--" and "-->" parts
  680.         if ((commentNode.data.length + 7) != (nodeOffsets[1] - nodeOffsets[0]))
  681.           inCurrentDoc = false;
  682.           
  683.         if (inCurrentDoc)
  684.           userData.push( dreamweaver.nodeToOffsets( commentNode ) );
  685.       }
  686.    }
  687.  
  688.    if (cbRemoveDWComments.checked && bIsDWComment)
  689.    {
  690.          userData.push( dreamweaver.nodeToOffsets( commentNode ) );
  691.    }
  692.  
  693.    return true;
  694. }
  695.  
  696. // processElement()
  697. //
  698. // Process a node of ELEMENT type within the user's document
  699. // This is a callback from traverse() used during the main
  700. // removal traversal.
  701. //
  702. function processElement( elementNode )
  703. {
  704.    // MM_note( "Processing element: " + elementNode.tagName );
  705.    // Remove specific tag(s) check
  706.    //
  707.    if ( cbRemoveTags.checked &&
  708.         arrayContains(arrTagsToRemove,  elementNode.tagName ) )
  709.    {
  710.       // MM_note( "* Removing specified tag " + elementNode.outerHTML );
  711.       if ( elementNode.outerHTML == elementNode.innerHTML )
  712.          elementNode.outerHTML = "";
  713.       else
  714.          elementNode.outerHTML = elementNode.innerHTML;
  715.  
  716.       numTagsRemoved++;
  717.    }
  718.    else
  719.    {
  720.       // Don't touch tags with style information
  721.       //
  722.       if ( !hasStyleAttribute( elementNode ) )
  723.       {
  724.          // Empty tag check
  725.          //
  726.  
  727.          if ( cbRemoveEmptyTags.checked &&
  728.          (isRemovableEmptyTag( elementNode )))
  729.          {
  730.             var parent = elementNode.parentNode;
  731.  
  732.             // MM_note( "* Removing empty tag: " + elementNode.outerHTML );
  733.             elementNode.outerHTML = "";
  734.             numEmptyRemoved++;
  735.  
  736.             // Small work around DW behavior -- paragraph tags with
  737.             // children are considered "not collapsable" even if the
  738.             // children are empty.  When we remove all empty children
  739.             // of a p tag then, DW sticks in a   to keep the
  740.             // remaining <p> from being collapsed -- this makes the <p>
  741.             // then come alive in the browser layout.  So if we've just
  742.             // zapped the last child of a p tag, rewrite the P tag without
  743.             // the   so it remains collapsed in the browser layout.
  744.             // Note that if the p tag originally had text or an  
  745.             // it would still have textual children after the empty tag
  746.             // removal and would be untouched.
  747.             //
  748.             if ( parent.tagName == "P" && !(parent.hasChildNodes()) )
  749.                parent.outerHTML = "<p>";
  750.          }
  751.          // Redundant child check
  752.          //
  753.          else
  754.          if ( cbRemoveRedundant.checked &&
  755.               hasRedundantParent( elementNode ) )
  756.          {
  757.             // MM_note( "* Removing redundant tag: " + elementNode.outerHTML );
  758.             elementNode.outerHTML = elementNode.innerHTML;
  759.             numRedundantRemoved++;
  760.          }
  761.          // Child/parent coalesce check
  762.          //
  763.          else
  764.          if ( cbCombineFonts.checked &&
  765.               arrayContains(combinableTagCandidates,  elementNode.tagName ) )
  766.          {
  767.             var parent  = findCombinableParent( elementNode, elementNode.tagName );
  768.             if ( parent != null )
  769.             {
  770.                // MM_note( "* Combining font tags: " + elementNode.outerHTML );
  771.  
  772.                // Set all child attributes on parent and remove child
  773.                //
  774.                var arrs    = parseAttributes( elementNode, true, false );
  775.                var attribs = arrs[0];
  776.                var values  = arrs[1];
  777.  
  778.                for( var i = 0; i < attribs.length; i++ )
  779.                   parent.setAttribute( attribs[i], values[i] ); // The value part
  780.                                                                 // here may be null
  781.                elementNode.outerHTML = elementNode.innerHTML;
  782.                numFontsCombined++;
  783.             }
  784.          }
  785.          // Dreamweaver comment check -- dreamweaver comments
  786.          // come back to us as element nodes rather than comment nodes
  787.          else
  788.          if ( cbRemoveDWComments.checked &&
  789.               arrayContains(arrDWCommentTags, elementNode.tagName ) )
  790.          {
  791.             // MM_note( "Removing DW comment: " + elementNode.tagName );
  792.             dreamweaver.editLockedRegions(true);
  793.             elementNode.outerHTML = elementNode.innerHTML;
  794.             numCommentsRemoved++;
  795.          }
  796.          else
  797.          if ( cbRemoveDWComments.checked &&
  798.               arrayContainsPrefix(arrDWTagPrefixes, elementNode.tagName ) )
  799.          {
  800.             // MM_note( "Removing DW tag: " + elementNode.tagName );
  801.             dreamweaver.editLockedRegions(true);
  802.  
  803.       // sn 8/6/01: for some reason, for empty tags, innerHTML
  804.       // returns the entire tag instead of the empty string.  (Anyway,
  805.       // this is what I observed for MMTInstance:Param tags.)  Tweaking
  806.       // to use "" instead of elementNode.innerHTML for such tags.
  807.       if ( elementNode.tagName == "MMTEMPLATE:EXPR"            ||
  808.          elementNode.tagName == "MMTEMPLATE:PARAM"           ||
  809.          elementNode.tagName == "MMTEMPLATE:PASSTHROUGHEXPR" ||
  810.          elementNode.tagName == "MMTINSTANCE:PARAM"          )
  811.         elementNode.outerHTML = "";
  812.       else
  813.         elementNode.outerHTML = elementNode.innerHTML;
  814.             numTagsRemoved++;
  815.          }
  816.       }
  817.    }
  818.  
  819.    return true; // continue traverse
  820. }
  821.  
  822. // emptyHeaderStateTextHandler()
  823. //
  824. // This text node callback is used by pass two to flip
  825. // the global bPreserveEmptyHeader state to true -- we
  826. // just encountered text, so the next empty header
  827. // found will force a carriage return and thus can't
  828. // be removed.  Empty headers after that however can
  829. // be removed until the next piece of text is encountered...
  830. //
  831. function emptyHeaderStateTextHandler( node )
  832. {
  833.    bPreserveEmptyHeader = true;
  834.    return true;
  835. }
  836.  
  837. // traverse()
  838. //
  839. // Do a recursive depth-first traversal of the user's
  840. // document starting from the given node.
  841. //
  842. // Callers provide up to three callback functions which
  843. // accept a node argument, one each (or the same one)
  844. // to process nodes of ELEMENT, TEXT, or COMMENT type.
  845. // At least one callback function is required.
  846. //
  847. // The handlers may stop the traversal by returning false;
  848. // returning true will continue the traversal to its
  849. // completion.
  850. //
  851. // A fourth argument, a handle to a some user variable to
  852. // be passed on to each callback, may also be provided.
  853. //
  854. function traverse( node, fElementHandler ) // optional: fTextHandler, fCommentHandler, userData )
  855. {
  856.    var fTextHandler  = traverse.arguments.length >= 3 ? traverse.arguments[2] : null;
  857.    var fCmmtHandler  = traverse.arguments.length >= 4 ? traverse.arguments[3] : null;
  858.    var userData      = traverse.arguments.length >= 5 ? traverse.arguments[4] : null;
  859.    var children      = node.childNodes;
  860.    var nChildren     = children.length;
  861.    var bContinue     = true;
  862.    var current       = null;
  863.  
  864.    for( var i = 0; bContinue && (i < nChildren); i++ )
  865.    {
  866.       current = children.item( i );
  867.  
  868.       // descend to any children first
  869.       if ( current.hasChildNodes() )
  870.          traverse( current, fElementHandler, fTextHandler, fCmmtHandler, userData );
  871.  
  872.       // process current node
  873.       switch( current.nodeType )
  874.       {
  875.          case Node.ELEMENT_NODE:
  876.             if ( userData != null )
  877.                bContinue = fElementHandler( current, userData );
  878.             else
  879.                bContinue = fElementHandler( current );
  880.             break;
  881.  
  882.          case Node.COMMENT_NODE:
  883.             if ( fCmmtHandler != null )
  884.                if ( userData != null )
  885.                   bContinue = fCmmtHandler( current, userData );
  886.                else
  887.                   bContinue = fCmmtHandler( current );
  888.             break;
  889.  
  890.          case Node.TEXT_NODE:
  891.             if ( fTextHandler != null )
  892.                if ( userData != null )
  893.                   bContinue = fTextHandler( current, userData )
  894.                else
  895.                   bContinue = fTextHandler( current )
  896.             break;
  897.  
  898.          case Node.DOCUMENT_NODE:
  899.          default:
  900.              MM_error( MSG_UnknownNodeType, current.nodeType );
  901.       }
  902.    }
  903. }
  904.  
  905. // doPassOne()
  906. //
  907. // Pass one does cleanup based on the HTML source string for
  908. // the user's document; currently that means comment and extra
  909. // whitespace removal.  Note that most DW comments are not handled
  910. // here (since they're internally represented as tags), but some
  911. // are.
  912. //
  913. // BW 8/17/98 Removed "remove extra whitespace" option for
  914. //            performance reasons
  915. //
  916. function doPassOne()
  917. {
  918.    if ( cbRemoveComments.checked || cbRemoveDWComments.checked )  // pass one options
  919.    {
  920.       var htmlstr = dreamweaver.getDocumentDOM( 'document' ).documentElement.outerHTML;
  921.       var htmlpos = 0;
  922.       var htmlarr = new Array(); // array to save newing of intermediate
  923.                                  // string copies of doc
  924.  
  925.       // To remove comments, traverse over the entire DOM gathering
  926.       // offsets into the HTML source of the comments to be removed,
  927.       // then remove those comments from the HTML source string.
  928.       //
  929.       var root           = dreamweaver.getDocumentDOM('document');
  930.       var commentOffsets = new Array();
  931.       var stubCallback   = new Function( "node", "userData", "return true;" );
  932.  
  933.       if ( root != null && root.hasChildNodes() )
  934.          traverse( root
  935.                  , stubCallback
  936.                  , stubCallback
  937.                  , loadCommentOffsets
  938.                  , commentOffsets );
  939.  
  940.       // Now use offsets to delete sections of text from
  941.       // within the document source string.
  942.       //
  943.       if ( commentOffsets.length > 0 )
  944.       {
  945.          var lastpos = 0;
  946.          for( var i = 0; i < commentOffsets.length; i++ )
  947.          {
  948.             htmlarr[htmlpos++] = htmlstr.substring( lastpos
  949.                                                   , commentOffsets[i][0] );
  950.             lastpos = commentOffsets[i][1];
  951.             numCommentsRemoved++;
  952.          }
  953.  
  954.          htmlarr[htmlpos++] = htmlstr.substring( lastpos );
  955.       }
  956.  
  957.       if ( htmlarr.length > 0 )
  958.          dreamweaver.getDocumentDOM( 'document' ).documentElement.outerHTML = htmlarr.join("");
  959.    }
  960. }
  961.  
  962. // doPassTwo()
  963. //
  964. // Pass two does cleanup on DOM objects as appropriate over the
  965. // course of traversing the DOM heirarchy.  The actual work in this
  966. // pass is done in the processElement() callback.
  967. //
  968. function doPassTwo()
  969. {
  970.    // Load up comma-separated list of tags to remove if any; warn
  971.    // if option is checked but no tags specified
  972.    //
  973.    arrTagsToRemove = dreamweaver.getTokens( tbTagsToRemove.value.toUpperCase(), ", " );
  974.    if ( cbRemoveTags.checked && arrTagsToRemove.length == 0 )
  975.       MM_error( MSG_NoTagsToRemove );
  976.  
  977.    // Traverse document, processing leaves
  978.    //
  979.    var root = dreamweaver.getDocumentDOM('document');
  980.  
  981.    if ( root != null && root.hasChildNodes() )
  982.    {
  983.       traverse( root
  984.               , processElement
  985.               , emptyHeaderStateTextHandler )
  986.  
  987.       // and finally attempt to remove tracingsrc attributes
  988.       // in body tag
  989.       //
  990.       removeTracingAttrs();
  991.    }
  992.    else
  993.       MM_error( MSG_ErrEmptyDoc );
  994.  
  995. }
  996.  
  997. // cleanUpDocument()
  998. //
  999. // Main routine for performing clean up when user hits OK.
  1000. // Clean up is done in three passes:
  1001. //
  1002. // Pass 1: Clean up certain items based on the entire HTML
  1003. //         document as a string
  1004. // Pass 2: Clean up certain items while traversing the DOM
  1005. //
  1006. function cleanUpDocument()
  1007. {
  1008.    // Set up logging particulars
  1009.    //
  1010.    if ( cbShowLog.checked )
  1011.    {
  1012.       MM_enableLogging();
  1013.       MM_clearLog();
  1014.    }
  1015.    else {
  1016.       MM_disableLogging();
  1017.    }
  1018.  
  1019.    // Do cleanup in two passes -- the first pass , the second pass
  1020.    // cleans up certain items based on a hierarchy traversal of the DOM.
  1021.    // Then, if the doc is XHTML, call the cleanupXHTML function.
  1022.    //
  1023.    MM.setBusyCursor();
  1024.    doPassOne();
  1025.    doPassTwo();
  1026.    if (dw.getDocumentDOM().getIsXHTMLDocument())
  1027.       arrXHTMLresults = dw.getDocumentDOM().cleanupXHTML(true);
  1028.    else
  1029.       arrXHTMLresults = 0;
  1030.    MM.clearBusyCursor();
  1031.    finalize();
  1032. }
  1033.  
  1034. // initialize()
  1035. //
  1036. // This is called on BODY onLoad; initialize all script globals
  1037. //
  1038. function initialize()
  1039. {
  1040.    // Counters for logging output
  1041.    //
  1042.    numEmptyRemoved      = 0;
  1043.    numRedundantRemoved  = 0;
  1044.    numTagsRemoved       = 0;
  1045.    numCommentsRemoved   = 0;
  1046.    numFontsCombined     = 0;
  1047.    bRemovedTracing      = false;
  1048.  
  1049.    arrTagsToRemove.length = 0; // Empty array
  1050.  
  1051.    strClassAttrib       = "CLASS";
  1052.    strStyleAttrib       = "STYLE";
  1053.  
  1054.    // The following tags represent the tag names of Dreamweaver-
  1055.    // specific comments, which are processed through the Dreamweaver
  1056.    // JS API/DOM as named element nodes rather than comment nodes
  1057.    //
  1058.    arrDWCommentTags.push ( "MM:EDITABLE"
  1059.                          , "MM:LIBITEM"       // variable library item (currently unused)
  1060.                          , "MM:TEMPLATE"
  1061.                          , "{#CUSTOMOBJ}"
  1062.                          , "{#IMEINLINE}"      // used by Japanese DW
  1063.                          , "{#LIBITEM}" );
  1064.  
  1065.    // The following strings are prefixes; any tag beginning with one of
  1066.    // these prefixes is Dreamweaver-specific markup.  (These represent
  1067.    // tags, not comments, but we still remove them when removing Dreamweaver-
  1068.    // specific comments.)
  1069.    //
  1070.    // NOTE (sn 6/22/01): by the time we get a tag name to compare with,
  1071.    // it's been converted to uppercase.  So, I'm including uppercase versions
  1072.    // of these strings.  However, I'm leaving the mixed-case versions in as
  1073.    // well, in case this ever changes.
  1074.    arrDWTagPrefixes.push ( "MMTemplate:", "MMTInstance:",
  1075.                  "MMTEMPLATE:", "MMTINSTANCE:" );
  1076.  
  1077.    // This array contains Dreamweaver comments that are actually
  1078.    // internally stored as comments (i.e., they aren't magically
  1079.    // transformed into tags the way the above tags are).
  1080.    arrDWCommentNonTags.push ( "#DefaultLayoutTable","DWLayoutDefaultTable","DWLayoutEmptyCell","DWLayoutTable" );
  1081.  
  1082.    // The following tags can be harmlessly removed from the user's
  1083.    // document if they're empty.  Note that the Heading tags are
  1084.    // not always safe and require special further handling; see
  1085.    // isEmptyRemoveableTag().
  1086.    //
  1087.    emptyRemovalCandidates.push ( "H1", "H2", "H3", "H4", "H5", "H6"
  1088.                                , "TT", "I", "B", "U", "STRIKE", "BIG"
  1089.                                , "SMALL", "SUB", "SUP", "EM", "STRONG"
  1090.                                , "DFN", "CODE", "SAMP", "KBD", "VAR"
  1091.                                , "CITE", "XMP", "BLINK"
  1092.                                , "ADDRESS"
  1093.                                , "A"
  1094.                                , "FONT"
  1095.                                , "SPAN"
  1096.                                , "TABLE"
  1097.                                , "BLOCKQUOTE"
  1098.                                , "LI", "OL", "UL"
  1099.                                , "DD", "DT", "DL"
  1100.                                , "DIR", "MENU"
  1101.                                , "DIV", "CENTER" );
  1102.  
  1103.    // These tags can be safely removed if they're redundant
  1104.    // with their immediate parent, i.e., this tags have
  1105.    // no nesting semantics.
  1106.    //
  1107.    redundantTagCandidates.push( "TT", "I", "B", "U", "S", "STRIKE", "BIG"
  1108.                               , "SMALL", "SUB", "SUP", "EM", "STRONG"
  1109.                               , "DFN", "CODE", "SAMP", "KBD", "VAR"
  1110.                               , "CITE", "XMP"
  1111.                               , "FONT"
  1112.                               , "CENTER"
  1113.                               , "SPAN" );
  1114.  
  1115.    // These tags can be safely coalesced with parents with identical
  1116.    // regions of influence.  Currently this is only done for FONT tags.
  1117.    //
  1118.    combinableTagCandidates.push( "FONT" );
  1119.  
  1120.  
  1121.    // Global used by pass two to indicate if the next empty
  1122.    // header we encounter should be preserved -- the first
  1123.    // empty header after text is significant as a carriage
  1124.    // return is forced; after that they can be gobbled until
  1125.    // there's more text.
  1126.    //
  1127.    bPreserveEmptyHeader = false;
  1128.  
  1129.  
  1130.    // And finally reference actual form element names
  1131.    // here once
  1132.    //
  1133.    with( document.optionsForm )
  1134.    {
  1135.       cbRemoveEmptyTags       = removeEmptyTags;
  1136.       cbRemoveRedundant       = removeRedundantChildren;
  1137.       cbRemoveComments        = removeNDWComments;
  1138.       cbRemoveDWComments      = removeDWComments;
  1139.       cbRemoveTags            = removeTag;
  1140.       cbCombineFonts          = combineFonts;
  1141.       cbShowLog               = showLog;
  1142.       tbTagsToRemove          = tagsToRemove;
  1143.    }
  1144. }
  1145.  
  1146.  
  1147.  
  1148. function finalize()
  1149. {
  1150.    // Show what we did if show log is enabled
  1151.    //
  1152.    if ( cbShowLog.checked )
  1153.    {
  1154.       MM_note( MSG_TrcSummaryHeader );
  1155.     var bLeftSomethingUnfixed =
  1156.  
  1157.           (arrXHTMLresults.length > 0) &&
  1158.           (
  1159.               (arrXHTMLresults[1] > 0)   ||
  1160.               (arrXHTMLresults[2] > 0)   ||
  1161.               (arrXHTMLresults[3] > 0)   ||
  1162.               (arrXHTMLresults[4] > 0)   ||
  1163.               (arrXHTMLresults[5] > 0)
  1164.           );
  1165.  
  1166.       var bDidSomething = (numEmptyRemoved > 0)      ||
  1167.                           (numRedundantRemoved > 0)  ||
  1168.                           (numTagsRemoved > 0)       ||
  1169.                           (numCommentsRemoved > 0)   ||
  1170.                           (numFontsCombined > 0)     ||
  1171.               (arrXHTMLresults[0] > 0)   ||
  1172.                           (bRemovedTracing);
  1173.  
  1174.       if ( bDidSomething || bLeftSomethingUnfixed )
  1175.       {
  1176.          if ( numEmptyRemoved > 0 )
  1177.             MM_note( MSG_TrcEmptyRemoved, numEmptyRemoved );
  1178.          if ( numRedundantRemoved > 0 )
  1179.             MM_note( MSG_TrcRedundantRemoved, numRedundantRemoved );
  1180.          if ( numTagsRemoved > 0 )
  1181.             MM_note( MSG_TrcTagsRemoved, numTagsRemoved );
  1182.          if ( numCommentsRemoved > 0 )
  1183.             MM_note( MSG_TrcCommentsRemoved, numCommentsRemoved );
  1184.          if ( numFontsCombined > 0 )
  1185.             MM_note( MSG_TrcFontsCombined, numFontsCombined );
  1186.          if ( bRemovedTracing )
  1187.             MM_note( MSG_TracingAttrsRemoved );
  1188.          if ( arrXHTMLresults[0] > 0)
  1189.             MM_note( MSG_XHTMLFixed );
  1190.  
  1191.          if ( bDidSomething && bLeftSomethingUnfixed )
  1192.             MM_note( "\n" );
  1193.          if ( arrXHTMLresults[1] > 0)
  1194.             MM_note( MSG_XHTMLMissingMapID, arrXHTMLresults[1]);
  1195.          if ( arrXHTMLresults[2] > 0)
  1196.             MM_note( MSG_XHTMLMissingScriptType, arrXHTMLresults[2] );
  1197.          if ( arrXHTMLresults[3] > 0)
  1198.             MM_note( MSG_XHTMLMissingStyleType, arrXHTMLresults[3] );
  1199.          if ( arrXHTMLresults[4] > 0)
  1200.             MM_note( MSG_XHTMLMissingImgAlt, arrXHTMLresults[4] );
  1201.          if ( arrXHTMLresults[5] > 0)
  1202.             MM_note( MSG_XHTMLMissingAreaAlt, arrXHTMLresults[5] );
  1203.       }
  1204.       else {
  1205.          MM_note( MSG_TrcDidNothing );
  1206.       }
  1207.       MM_showLog();
  1208.    }
  1209.  
  1210.    window.close();
  1211. }
  1212.  
  1213.  
  1214. function setMenuText()
  1215. {
  1216.   if (dw.getDocumentDOM() && dw.getDocumentDOM().getIsXHTMLDocument())
  1217.     return MENU_CleanupXHTML;
  1218.   else
  1219.     return MENU_CleanupHTML;
  1220. }
  1221.